﻿using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Linq;
using System.Linq.Expressions;
using Medora.Core;
using Medora.DataContract;
using Medora.Model.Basic;

namespace Medora.Data
{
   
    public class EFRepository<T> : IRepository<T> where T : class
    {
        protected ISortConstructor<T> _sortConstructor;

        public EFRepository(DbContext dbContext)
        {
            if (dbContext == null)
                throw new ArgumentNullException("dbContext");
            DbContext = dbContext;
            DbSet = DbContext.Set<T>();  
            _sortConstructor = new SortConstructor<T>();
        }

   
        protected DbContext DbContext { get; set; }

   
        protected DbSet<T> DbSet { get; set; }

   
        public virtual IQueryable<T> GetAll()
        {
            return DbSet;
        }

        public virtual T GetById(Guid id)
        {
            return DbSet.Find(id);
        }

        public IEnumerable<T> Get(Expression<Func<T, bool>> filter = null, Func<IQueryable<T>, IOrderedQueryable<T>> orderBy = null, string includeProperties = "")
        {
            IQueryable<T> query = DbSet;

            if (filter != null)
            {
                query = query.Where(filter);
            }

            foreach (var includeProperty in includeProperties.Split
                (new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
            {
                query = query.Include(includeProperty);
            }

            if (orderBy != null)
            {
                return orderBy(query);
            }
            else
            {
                return query;
            }
        }

        public int GetCount(Expression<Func<T, bool>> filter = null)
        {
            IQueryable<T> query = DbSet;

            if (filter != null)
            {
                query = query.Where(filter);
            }
            
            return query.Count();
        }


        public IEnumerable<T> GetTake(int pageSize, int page, Func<IQueryable<T>, IOrderedQueryable<T>> orderBy, Expression<Func<T, bool>> filter = null, string includeProperties = "")
        {
            IQueryable<T> query = DbSet;

            if (filter != null)
            {
                query = query.Where(filter);
            }

            foreach (var includeProperty in includeProperties.Split
                (new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
            {
                query = query.Include(includeProperty);
            }

            if (orderBy != null)
            {
                return orderBy(query).Skip((page - 1)*pageSize).Take(pageSize);
            }
            return query.Take(pageSize);
        }

        public IEnumerable<T> GetTake(int pageSize, int page, Expression<Func<T, bool>> filter = null, SortInfo sortInfo=null, string includeProperties = "")
        {
            IQueryable<T> query = DbSet;

            if (filter != null)
            {
                query = query.Where(filter);
            }

            foreach (var includeProperty in includeProperties.Split
                (new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
            {
                query = query.Include(includeProperty);
            }


             if (sortInfo != null)
             {
                 sortInfo.Field = sortInfo.Field.FirstCharToUpper();

                 var propertyType = typeof (T).GetProperty(sortInfo.Field).PropertyType.ToString();
                 switch (propertyType)
                 {
                     case "System.DateTime":
                         var dateExpr = _sortConstructor.GetSortExpessionByDateTime(sortInfo.Field);
                         return sortInfo.Direction == SortOrderType.Asc
                                    ? query.OrderBy(dateExpr).Skip((page - 1)*pageSize).Take(pageSize)
                                    : query.OrderByDescending(dateExpr).Skip((page - 1)*pageSize).Take(pageSize);
                     case "System.Int16":
                     case "System.Int32":
                     case "System.Int64":
                         var intExpr = _sortConstructor.GetSortExpessionByInt(sortInfo.Field);
                         return sortInfo.Direction == SortOrderType.Asc
                                    ? query.OrderBy(intExpr).Skip((page - 1)*pageSize).Take(pageSize)
                                    : query.OrderByDescending(intExpr).Skip((page - 1)*pageSize).Take(pageSize);
                     case "System.Double":
                     case "System.Decimal":
                         var doubleExpr = _sortConstructor.GetSortExpessionByDouble(sortInfo.Field);
                         return sortInfo.Direction == SortOrderType.Asc
                                    ? query.OrderBy(doubleExpr).Skip((page - 1)*pageSize).Take(pageSize)
                                    : query.OrderByDescending(doubleExpr).Skip((page - 1)*pageSize).Take(pageSize);
                     default:
                         var stringExpr = _sortConstructor.GetSortExpessionByString(sortInfo.Field);
                         return sortInfo.Direction == SortOrderType.Asc
                                    ? query.OrderBy(stringExpr).Skip((page - 1)*pageSize).Take(pageSize)
                                    : query.OrderByDescending(stringExpr).Skip((page - 1)*pageSize).Take(pageSize);
                 }

             }
             return query.Take(pageSize);
        }


        public virtual void Add(T entity)
        {
            DbEntityEntry dbEntityEntry = DbContext.Entry(entity);
            if (dbEntityEntry.State != EntityState.Detached)
            {
                dbEntityEntry.State = EntityState.Added;
            }
            else
            {
                DbSet.Add(entity);
            }
        }
        
        public virtual void Update(T entity)
        {
            DbEntityEntry dbEntityEntry = DbContext.Entry(entity);
            if (dbEntityEntry.State == EntityState.Detached)
            {
                DbSet.Attach(entity);
            }
            dbEntityEntry.State = EntityState.Modified;
        }

        public virtual void Delete(T entity)
        {
            DbEntityEntry dbEntityEntry = DbContext.Entry(entity);
            if (dbEntityEntry.State != EntityState.Deleted)
            {
                dbEntityEntry.State = EntityState.Deleted;
            }
            else
            {
                DbSet.Attach(entity);
                DbSet.Remove(entity);
            }
        }

        public virtual void Delete(Guid id)
        {
            var entity = GetById(id);
            if (entity == null) return; // not found; assume already deleted.
            Delete(entity);
        }
        
        public virtual void DeleteWhere(Expression<Func<T, bool>> filter)
        {
            var entities = Get(filter);
            foreach (var entity in entities)
            {
                Delete(entity);
            }
        }

        public void OneFieldUpdate(T entity, Expression<Func<T, object>> field)
        {
           DbEntityEntry dbEntityEntry = DbContext.Entry(entity);
           if (dbEntityEntry.State == EntityState.Detached)
           {
                DbSet.Attach(entity);
                DbContext.Entry(entity).Property(field).IsModified = true;
           }
        
        }

        public void FieldSetUpdate(T entity, List<Expression<Func<T, object>>> fields)
        {
            DbEntityEntry dbEntityEntry = DbContext.Entry(entity);
            if (dbEntityEntry.State == EntityState.Detached)
            {
                DbSet.Attach(entity);
            }

            foreach (var field in fields)
            {
                DbContext.Entry(entity).Property(field).IsModified = true;
            }

        }

    }
}
